/*
 * Copyright 2007 Stefan Arentz <stefan@arentz.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "NSStringAdditions.h"
#import "SMAEncoder.h"
#import "SMADigester.h"
#import "SBJSON.h"
#import "WebServiceClient.h"

@interface WebServiceClient (Private)

- (NSURLRequest*) _createURLRequestWithWebServiceRequest: (WebServiceRequest*) webServiceRequest;
- (NSString*) _formEncodeString: (NSString*) string;
- (NSString*) _generateTimestamp;
- (NSString*) _generateSignatureForParameters: (NSDictionary*) dictionary password: (NSString*) password;

@end

@implementation WebServiceClient

@synthesize webServiceRequest = _webServiceRequest;
@synthesize delegate = _delegate;

- (id) initWithWebServiceRequest: (WebServiceRequest*) webServiceRequest delegate: (id) delegate
{
   if ((self = [super init]) != nil)
   {
      _webServiceRequest = webServiceRequest;
      _delegate = delegate;
   
      _connection = [[NSURLConnection alloc] initWithRequest: [self _createURLRequestWithWebServiceRequest: webServiceRequest] delegate: self]; // XXX Can return nil?
      if (_connection == nil) {
         return nil;
      }
      
      _responseData = [[NSMutableData data] retain];
      _json = [[SBJSON alloc] init];
   }
   return self;
}

+ (id) webServiceClientWithWebServiceRequest: (WebServiceRequest*) request delegate: (id) delegate
{
   return [[[self alloc] initWithWebServiceRequest: request delegate: delegate] autorelease];
}

- (void) start
{
   //[connection start];
}

//

- (void) connection: (NSURLConnection*) connection didReceiveResponse: (NSURLResponse*) response
{
   // XXX Sample code says this is called before each redirect?
   [_responseData setLength: 0];
}

- (void) connection: (NSURLConnection*) connection didReceiveData: (NSData*) data
{
   [_responseData appendData: data];
}

- (void) connection: (NSURLConnection*) connection didFailWithError: (NSError*) error
{
   @try {
      [_delegate webServiceClient: self didFailWithError: error];
   } @catch (id exception) {
      NSLog(@"An unhandled exception was caught while calling the WebServiceClient delegate's webServiceClient:didFailWithError: %@", exception);
   }
}

- (void) connectionDidFinishLoading: (NSURLConnection*) connection
{
   NSError* error = nil;
   id object = [_json objectWithString: [NSString stringWithData: _responseData encoding: NSUTF8StringEncoding] error: &error];
   if (error != nil) {
      @try {
         [_delegate webServiceClient: self didFailWithError: error];
      } @catch (id exception) {
         NSLog(@"An unhandled exception was caught while calling the WebServiceClient delegate's webServiceClient:didFailWithError: %@", exception);
      }
   } else {
      @try {
         [_delegate webServiceClient: self didFinishWithResponse: object];
      } @catch (id exception) {
         NSLog(@"An unhandled exception was caught while calling the WebServiceClient delegate's webServiceClient:didFinishWithResponse: %@", exception);
      }
   }
}

+ (NSDictionary*) sendSynchronousRequest: (WebServiceRequest*) request error: (NSError*) error
{
   return nil;
}

@end

//

@implementation WebServiceClient (Private)

- (NSURLRequest*) _createURLRequestWithWebServiceRequest: (WebServiceRequest*) webServiceRequest;
{
   // Convert the web service parameters to URL parameters

   NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
   
   for (id key in [webServiceRequest.parameters allKeys]) {
      id value = [webServiceRequest.parameters objectForKey: key];
      if ([value conformsToProtocol: @protocol(NSFastEnumeration)]) {
         int i = 0;
         for (id e in value) {
            [parameters setObject: [e stringValue] forKey: [NSString stringWithFormat: @"%@.%d", key, i++]];
         }
      } else {
         [parameters setObject: [webServiceRequest.parameters objectForKey: key] forKey: key];
      }
   }

   [parameters setObject: webServiceRequest.action forKey: @"Action"];
   [parameters setObject: webServiceRequest.version forKey: @"Version"];

   if (webServiceRequest.username != nil && webServiceRequest.password != nil)
   {
      [parameters setObject: webServiceRequest.username forKey: @"AccessKey"];
      [parameters setObject: @"1" forKey: @"SignatureVersion"];
      [parameters setObject: [self _generateTimestamp] forKey: @"Timestamp"];
      [parameters setObject: [self _generateSignatureForParameters: parameters password: webServiceRequest.password] forKey: @"Signature"];
   }

   // Create the POST data
   
   NSMutableString* body = [NSMutableString stringWithCapacity: 1024];
   for (id key in [parameters allKeys]) {
      if ([body length] != 0) {
         [body appendString: @"&"];
      }
      [body appendFormat: @"%@=%@", key, [self _formEncodeString: [parameters objectForKey: key]]];
   }

   // Create a POST request

   NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL: webServiceRequest.url];
   [request setHTTPMethod: @"POST"];
   [request setValue: @"application/x-www-form-urlencoded" forHTTPHeaderField: @"Content-Type"];
   [request setValue: [[NSNumber numberWithInteger: [body length]] stringValue] forHTTPHeaderField: @"Content-Length"];
   [request setHTTPBody: [body dataUsingEncoding: NSUTF8StringEncoding]];
   
   return request;
}

- (NSString*) _formEncodeString: (NSString*) string
{
   NSString* encoded = (NSString*) CFURLCreateStringByAddingPercentEscapes(
      kCFAllocatorDefault,
      (CFStringRef) string,
      NULL,
      CFSTR("!*'();:@&=+$,/?%#[]"),
      kCFStringEncodingUTF8
   );
   
   return [encoded autorelease];
}

- (NSString*) _generateTimestamp
{
   return @"2008-06-28T17:26:08+00:00";
}

- (NSString*) _generateSignatureForParameters: (NSDictionary*) parameters password: (NSString*) password
{
   NSMutableString* signature = [NSMutableString string];

   for (id key in [[parameters allKeys] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)]) {
      [signature appendString: key];
      [signature appendString: [parameters objectForKey: key]];
   }
   
   return (NSString*) SMAEncodeBase64FromData(
      kCFAllocatorDefault,
      SMACreateSHA1HMACDigestFromString(
         kCFAllocatorDefault,
         (CFStringRef) signature,
         kCFStringEncodingUTF8,
         (CFDataRef) [password dataUsingEncoding: NSUTF8StringEncoding]
      )
   );
}

@end
